﻿/******************************************************************************
* SunnyUI.FrameDecoder 开源TCP、串口数据解码库。
* CopyRight (C) 2012-2023 ShenYongHua(沈永华).
* QQ群：56829229 QQ：17612584 EMail：SunnyUI@qq.com
*
* Blog:   https://www.cnblogs.com/yhuse
* Gitee:  https://gitee.com/yhuse/SunnyUI
* GitHub: https://github.com/yhuse/SunnyUI
*
* SunnyUI.dll can be used for free under the MulanPSL2 license.
* If you use this code, please keep this note.
* 如果您使用此代码，请保留此说明。
******************************************************************************
* 文件名称: UComPort.cs
* 文件说明: 串口扩展类
* 当前版本: V1.0
* 创建日期: 2022-11-01
*
* 2022-11-01: V1.0.0 增加文件说明
* 2024-11-04: V6.3.5 重构
******************************************************************************/

using System;
using System.IO.Ports;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;

namespace Sunny.Com;

/// <summary>
/// 串口扩展类
/// </summary>
public class ComPort : IDisposable
{
    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="dataType">串口数据类型</param>
    /// <param name="portName">串口名称</param>
    /// <param name="baudRate">波特率</param>
    /// <param name="parity">奇偶校验</param>
    /// <param name="dataBits">数据位</param>
    /// <param name="stopBits">停止位</param>
    public ComPort(ComDataType dataType, string portName, int baudRate = 9600, Parity parity = Parity.None, int dataBits = 8, StopBits stopBits = StopBits.One)
    {
        Name = GetType().Name;
        Statistics = new ComStatistics();
        DataType = dataType;

        _comm = new SerialPort
        {
            PortName = portName,
            BaudRate = baudRate,
            Parity = parity,
            DataBits = dataBits,
            StopBits = stopBits,
            ParityReplace = 0
        };

        _comm.DataReceived += SerialPortDataReceived;
        _comm.ErrorReceived += Comm_ErrorReceived;
        _comm.PinChanged += Comm_PinChanged;
    }

    #region IDisposable

    /// <summary>
    /// 是否释放标志，保证重复释放资源时，不重复释放
    /// </summary>
    public bool IsDisposed { get; private set; }

    /// <summary>
    /// 判断是否已经被释放，如果是，则抛出异常。
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void ThrowIfDisposed()
    {
#if NET8_0_OR_GREATER
        ObjectDisposedException.ThrowIf(IsDisposed, this.GetType());
#else
        if (IsDisposed) throw new ObjectDisposedException(this.GetType().FullName);
#endif
    }

    /// <summary>
    /// 实现IDisposable中的Dispose方法
    /// </summary>
    public void Dispose()
    {
        // 必须为true
        Dispose(true);

        // 通知GC垃圾回收机制不再调用终结器（析构器）
        GC.SuppressFinalize(this);
    }

    /// <summary>
    /// 释放对象
    /// </summary>
    /// <param name="disposing">ture时一般位主动调用释放，false一般为GC调用析构函数时释放</param>
    protected virtual void Dispose(bool disposing)
    {
        if (!IsDisposed && disposing)
        {
            IsDisposed = true;

            //释放资源
            Close();
            _comm.Dispose();
        }
    }

    #endregion

    /// <summary>
    /// 串口信息统计类
    /// </summary>
    public ComStatistics Statistics { get; }

    /// <summary>
    /// 名称
    /// </summary>
    public string Name { get; set; }

    /// <summary>
    /// 标签
    /// </summary>
    public object Tag { get; set; }

    private readonly SerialPort _comm;

    private void Comm_PinChanged(object sender, SerialPinChangedEventArgs e)
    {
        if (IsDisposed) return;
        PinChanged?.Invoke(this, e);
    }

    private void Comm_ErrorReceived(object sender, SerialErrorReceivedEventArgs e)
    {
        if (IsDisposed) return;
        ErrorReceived?.Invoke(this, e);
    }

    /// <summary>
    /// 指示通过串口对象表示的端口上发生了错误
    /// </summary>
    public event SerialErrorReceivedEventHandler ErrorReceived;

    /// <summary>
    /// 指示通过串口对象表示的端口上发生非数据信号事件
    /// </summary>
    public event SerialPinChangedEventHandler PinChanged;

    /// <summary>
    /// 得到最接近数字且大于数字的二的N次方数
    /// </summary>
    /// <param name="value">数字</param>
    /// <returns>二的N次方数</returns>
    private static int Get2PowNHigh(int value)
    {
        --value;//避免正好输入一个2的次方数
        value |= value >> 1;
        value |= value >> 2;
        value |= value >> 4;
        value |= value >> 8;
        value |= value >> 16;
        return ++value;
    }

    /// <summary>
    /// 输入缓冲区，必须为2的N次方
    /// </summary>
    public int ReadBufferSize
    {
        get => IsDisposed ? 0 : _comm.ReadBufferSize;
        set
        {
            if (!IsDisposed) _comm.ReadBufferSize = Get2PowNHigh(value);
        }
    }

    /// <summary>
    /// 输出缓冲区，必须为2的N次方
    /// </summary>
    public int WriteBufferSize
    {
        get => IsDisposed ? 0 : _comm.WriteBufferSize;
        set
        {
            if (!IsDisposed) _comm.WriteBufferSize = Get2PowNHigh(value);
        }
    }

    /// <summary>
    /// 串口数据类型
    /// </summary>
    public ComDataType DataType { get; }

    /// <summary>
    /// 串口
    /// </summary>
    public SerialPort Instance => _comm;

    /// <summary>
    /// 串口是否打开
    /// </summary>
    public bool IsOpen => !IsDisposed && _comm.IsOpen;

    private bool _isClosing;
    private bool _isListening;

    /// <summary>
    /// 关闭串口
    /// </summary>
    public void Close()
    {
        if (IsDisposed) return;
        if (!IsOpen) return;

        _isClosing = true;
        while (_isListening) Thread.Sleep(100);
        _comm.Close();
        _isClosing = false;
    }

    /// <summary>
    /// 打开串口
    /// </summary>
    public void Open()
    {
        Close();
        _comm.Open();
    }

    /// <summary>
    /// 打开串口
    /// </summary>
    /// <param name="portName">串口名称</param>
    public void ReOpen(string portName)
    {
        Close();
        _comm.PortName = portName;
        _comm.Open();
    }

    /// <summary>
    /// 打开串口
    /// </summary>
    /// <param name="portName">串口名称</param>
    public bool TryReOpen(string portName)
    {
        Close();
        _comm.PortName = portName;

        try
        {
            _comm.Open();
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }

        return IsOpen;
    }

    /// <summary>
    /// 尝试打开串口
    /// </summary>
    /// <returns></returns>
    public bool TryOpen()
    {
        Close();

        try
        {
            _comm.Open();
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }

        return IsOpen;
    }

    /// <summary>
    /// 读取数据间隔时间
    /// </summary>
    public TimeSpan ReadTimeSpan { get; set; } = TimeSpan.Zero;

    private void SerialPortDataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        if (IsDisposed) return;
        if (_isClosing) return;

        try
        {
            _isListening = true;
            if (DataType == ComDataType.ASCII)
            {
                OnReceived(this, new ComDataEventArgs() { String = ReadCommString(), Buffer = null });
            }

            if (DataType == ComDataType.HEX)
            {
                OnReceived(this, new ComDataEventArgs() { String = String.Empty, Buffer = ReadCommBytes() });
            }

            if (ReadTimeSpan > TimeSpan.Zero)
            {
                Thread.Sleep(ReadTimeSpan);
            }
        }
        finally
        {
            _isListening = false;
        }
    }

    /// <summary>
    /// 接收串口数据
    /// </summary>

    public event ConnectedEventHandler Received;

    /// <summary>
    /// 接收串口数据
    /// </summary>
    /// <param name="sender">对象</param>
    /// <param name="e">参数</param>
    protected virtual void OnReceived(ComPort sender, ComDataEventArgs e)
    {
        Received?.Invoke(sender, e);
    }

    private string ReadCommString()
    {
        string result = _comm.ReadExisting();
        Statistics.AddReceivedBytes(result.Length);
        return result;
    }

    private byte[] ReadCommBytes()
    {
        int bytesRead = _comm.BytesToRead;
        byte[] dataBytes = new byte[bytesRead];
        //读取缓冲区数据
        _comm.Read(dataBytes, 0, bytesRead);
        Statistics.AddReceivedBytes(bytesRead);
        return dataBytes;
    }

    /// <summary>
    /// 发送数据
    /// </summary>
    /// <param name="buffer">数据</param>
    public void Write(byte[] buffer)
    {
        Write(buffer, 0, buffer.Length);
    }

    /// <summary>
    /// 发送数据
    /// </summary>
    /// <param name="buffer">数据</param>
    /// <param name="offset">偏移</param>
    /// <param name="count">长度</param>
    public void Write(byte[] buffer, int offset, int count)
    {
        if (IsDisposed) return;
        if (!IsOpen) return;

        if (offset < 0)
        {
            throw new ArgumentOutOfRangeException(nameof(offset), $"Offset must be greater than or equal to zero.");
        }

        if (offset + count > buffer.Length)
        {
            throw new ArgumentOutOfRangeException(nameof(count), $"count must be less than write position.");
        }

        Statistics.AddSentBytes(count);
        _comm.Write(buffer, offset, count);
    }

    /// <summary>
    /// 发送命令
    /// </summary>
    /// <param name="cmdString">数据</param>
    /// <param name="addNewLine">是否增加换行字符串</param>
    public void Write(string cmdString, bool addNewLine = false)
    {
        if (IsDisposed) return;
        if (!IsOpen) return;

        Statistics.AddSentBytes(cmdString.Length);
        if (!addNewLine)
        {
            _comm.WriteLine(cmdString);
        }
        else
        {
            _comm.WriteLine(cmdString + Environment.NewLine);
        }
    }

    /// <summary>
    /// 获取当前设备所有串口列表
    /// </summary>
    /// <returns>串口列表</returns>
    public static string[] GetPortNames()
    {
        return SerialPort.GetPortNames();
    }

    /// <summary>
    /// 获取发送缓冲区中数据的字节数
    /// </summary>
    public int BytesToWrite => IsDisposed ? 0 : _comm.BytesToWrite;

    /// <summary>
    /// 获取接收缓冲区中数据的字节数
    /// </summary>
    public int BytesToRead => IsDisposed ? 0 : _comm.BytesToRead;

    /// <summary>
    /// 获取或者设置中断信号状态
    /// </summary>
    public bool BreakState
    {
        get => _comm.BreakState;
        set => _comm.BreakState = value;
    }

    /// <summary>
    /// 获取端口的载波检测行的状态
    /// </summary>
    public bool CDHolding => _comm.CDHolding;

    /// <summary>
    /// 获取“可以发送”行的状态
    /// </summary>
    public bool CtsHolding => _comm.CtsHolding;

    /// <summary>
    /// 获取或者设置一个值，该值指示 null 字节在端口和接收缓冲区之间传输时是否被忽略
    /// </summary>
    public bool DiscardNull
    {
        get => _comm.DiscardNull;
        set => _comm.DiscardNull = value;
    }

    /// <summary>
    /// 获取数据设置就绪（DSR）信号的状态
    /// </summary>
    public bool DsrHolding => _comm.DsrHolding;

    /// <summary>
    /// 获取或设置一个值，该值在串行通信过程中启用数据终端就绪（DTR）信号
    /// </summary>
    public bool DtrEnable
    {
        get => _comm.DtrEnable;
        set => _comm.DtrEnable = value;
    }

    /// <summary>
    /// 使用 Handshake 中的值获取或者设置串行端口数据传输的握手协议
    /// </summary>
    public Handshake Handshake
    {
        get => _comm.Handshake;
        set => _comm.Handshake = value;
    }

    /// <summary>
    /// 获取或者设置用于解释读写行方法调用结束的值
    /// </summary>
    public string NewLine
    {
        get => _comm.NewLine;
        set => _comm.NewLine = value;
    }

    /// <summary>
    /// 获取或设置一个字节，该字节在发生奇偶校验错误时替换数据流中的无效字节
    /// </summary>
    public byte ParityReplace
    {
        get => _comm.ParityReplace;
        set => _comm.ParityReplace = value;
    }

    /// <summary>
    /// 获取或者设置读取操作未完成时发生超时之前的毫秒数
    /// </summary>
    public int ReadTimeout
    {
        get => _comm.ReadTimeout;
        set => _comm.ReadTimeout = value;
    }

    /// <summary>
    /// 获取或者设置 DataReceived 事件发生前内部输入缓冲区中的字节数
    /// </summary>
    public int ReceivedBytesThreshold
    {
        get => _comm.ReceivedBytesThreshold;
        set => _comm.ReceivedBytesThreshold = value;
    }

    /// <summary>
    ///  获取或设置一个值，该值指示在串行通信中是否启用请求发送（RTS）信号
    /// </summary>
    public bool RtsEnable
    {
        get => _comm.RtsEnable;
        set => _comm.RtsEnable = value;
    }

    /// <summary>
    /// 获取或者设置写入操作未完成时发生超时之前的毫秒数
    /// </summary>
    public int WriteTimeout
    {
        get => _comm.WriteTimeout;
        set => _comm.WriteTimeout = value;
    }

    /// <summary>
    /// 获取或者设置通信端口，包括但不限于所有可用的 COM 端口
    /// </summary>
    public string PortName
    {
        get => _comm.PortName;
        set => _comm.PortName = value;
    }

    /// <summary>
    /// 获取或者设置串行波特率
    /// </summary>
    public int BaudRate
    {
        get => _comm.BaudRate;
        set => _comm.BaudRate = value;
    }

    /// <summary>
    /// 获取或者设置每个字节的标准数据位长度
    /// </summary>
    public int DataBits
    {
        get => _comm.DataBits;
        set => _comm.DataBits = value;
    }

    /// <summary>
    /// 获取或者设置奇偶校验检查协议
    /// </summary>
    public Parity Parity
    {
        get => _comm.Parity;
        set => _comm.Parity = value;
    }

    /// <summary>
    /// 获取或者设置每个字节的标准停止位数
    /// </summary>
    public StopBits StopBits
    {
        get => _comm.StopBits;
        set => _comm.StopBits = value;
    }

    /// <summary>
    /// 丢弃来自串行驱动程序中接收缓冲区的数据
    /// </summary>
    public void DiscardInBuffer()
    {
        if (IsDisposed) return;
        _comm.DiscardInBuffer();
    }

    /// <summary>
    /// 丢弃来自串行驱动程序中传输缓冲区的数据
    /// </summary>
    public void DiscardOutBuffer()
    {
        if (IsDisposed) return;
        _comm.DiscardOutBuffer();
    }

    /// <summary>
    /// 获取或者设置传输前后文本转换的字节编码
    /// </summary>
    public Encoding Encoding
    {
        get => _comm?.Encoding;
        set => _comm.Encoding = value;
    }
}